In this document I will further clean up the data and remove cells that most likely represent doublets, damaged cells or red blood cell contamination.

Removal of doublets

The following strategy for doubled removal was employed, somewhat following an approach from Pijuana-Sala, Griffiths and Guibentif et al.

  1. Estimate probability of cell being a doublet per sample using doubletCells from scran.
  2. Compute per sample clusters based on all genes based on a SNNGraph (k=10) and walktrap clustering with high resolution (steps=2, see 05_computeDoubletScores.R).
  3. Clusters with high average doublet probability are identified as doublets.
  4. All cells and samples (incl. doublets) are integrated using fastMNN, cells that cluster with “known” doublets cells are guilty by association.

Per sample clusters

  • Below are the per sample clusters as well as the doublet scores per cell.
  • Based on the UMAP shows patterns indicative of a lot of multiplets WKBR75.4bMG3. Looking at the Barcode plot in 01_MakeCountMatrix.nb.html it doesn’t show the same clear knee plot, potentially indicative of high levels of background mRNA. I will need to see whether to keep this samples or not.
# Identifying doublets with doubletCluster
library(ggplot2)
library(scran)
library(cowplot)
library(wesanderson)
library(viridis)
theme_set(theme_cowplot())

# Read in data
ddata <- readRDS("../data/Robjects/DoubletData.rds")

# Add sample index to data frames
# Also I am log transforming the scores here already
# Since Bioc10 the scores have a different distribution, with a much wider range
nms <- names(ddata)
ddata <- lapply(names(ddata), function(x) {
        dfs <- ddata[[x]]
        dfs$Sample <- x
        dfs$DbltScore <- log10(dfs$DbltScore)
        return(dfs)
})
names(ddata) <- nms

# Visualizing the clustering
cluster_plots <- lapply(names(ddata), function(nm) {
           x <- data.frame(ddata[[nm]])
           p <- ggplot(x, aes(x=SubUMAP1, y=SubUMAP2, color=factor(Cluster))) +
                   geom_point(size=0.7) +
                   scale_color_manual(values=wes_palette("FantasticFox1",length(unique(x$Cluster)),type="continuous")) +
                   theme(legend.position="none", panel.background = element_rect(colour = "black", linetype = 1, size = 0.5),
                     axis.line = element_blank(),
                     axis.ticks = element_blank(),
                     axis.text = element_blank(),
                     axis.title = element_blank(),) +
                   ggtitle(paste0(nm," (n=",nrow(x),", k=",length(unique(x$Cluster)),")")) 
               return(p)
})
names(cluster_plots) <- names(ddata)
plot_grid(plotlist=cluster_plots)

# Visualizing the doublet scores
score_plots <- lapply(names(ddata), function(nm) {
           x <- data.frame(ddata[[nm]])
           p <- ggplot(x, aes(x=SubUMAP1, y=SubUMAP2, color=DbltScore)) +
                   geom_point(size=0.7) +
                   scale_color_viridis() +
                   theme(legend.position="none", panel.background = element_rect(colour = "black", linetype = 1, size = 0.5),
                     axis.line = element_blank(),
                     axis.ticks = element_blank(),
                     axis.text = element_blank(),
                     axis.title = element_blank(),) +
                   ggtitle(paste0(nm," (n=",nrow(x),", k=",length(unique(x$Cluster)),")")) 
               return(p)
})
names(score_plots) <- names(ddata)
plot_grid(plotlist=score_plots)

Identify clusters per samples that represent doublets

  • Clusters that had a higher than median + 1.5xmad doublet score and where present at a frequency of less than 5% where identified as doublet cells
  • 1.5 as this seemed to produce the most reasonable results in terms of stringency
  • At this point I want to be rather stringent in the definition as it is more problematic to remove biologically relevant clusters than maintaining doublet clusters as they can still be flagged later.
# Identifying doublets based on doubletCell

ddatadf <- data.frame(do.call(rbind,ddata))

# Compute median scores per cluster and sample
medscores <- aggregate(ddatadf$DbltScore, list(ddatadf$Sample, ddatadf$Cluster), median)
names(medscores) = c("sample", "cluster", "median.score")

medscores$n.cells = sapply(1:nrow(medscores), function(row){
                   sum(ddatadf$Cluster == medscores$cluster[row] & ddatadf$Sample == medscores$sample[row])
})

medscores$fracCells = sapply(1:nrow(medscores), function(row){
                  medscores$n.cells[row]/sum(medscores$n.cells[medscores$sample == medscores$sample[row]])
})


# Define doublets based on fraction and median doublet score
medscores$outlier <- scater::isOutlier(medscores$median.score,nmad=1.5,type="higher",log=FALSE)
medscores$isDoublet <- medscores$outlier & medscores$fracCells <= 0.05

# Plot from Johnny with the significant clusters highlighted
ggplot(medscores, aes(x = sample, y = median.score, col = factor(cluster))) +
  geom_point(data = medscores[medscores$isDoublet,]) +
  geom_jitter(data = medscores[!medscores$isDoublet,], col = "darkgrey", size = 0.4, width = 0.2, height = 0) +
  guides(color="none") +
  labs(x = "Sample", y = "cluster median score") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

medscores.dbls <- medscores[medscores$isDoublet,]
smp.dbltCluster <- paste(medscores.dbls$sample,medscores.dbls$cluster,sep="_")

Visualizing the doublet called clusters

  • most clusters show the typical tear shape in the UMAP
  • some clusters also appear to bel self-doublets, which are less problematic but will still be removed
  • however we can already see here that there might be more doublet clusters than we identified above
smps <- names(ddata)[names(ddata) %in% medscores.dbls$sample]
doublet_plots <- lapply(smps, function(nm) {
           x <- data.frame(ddata[[nm]])
           p <- ggplot(x, aes(x=SubUMAP1, y=SubUMAP2)) +
                   geom_point(color="grey80",size=0.7) +
                   geom_point(data=x[x$Cluster %in% medscores.dbls[medscores.dbls$sample==nm,"cluster"],], aes(color=factor(Cluster))) +
                   #                                scale_color_manual(values=wes_palette("FantasticFox1",length(unique(x$Cluster)),type="continuous")) +
                   theme(legend.position="none", panel.background = element_rect(colour = "black", linetype = 1, size = 0.5),
                     axis.line = element_blank(),
                     axis.ticks = element_blank(),
                     axis.text = element_blank(),
                     axis.title = element_blank(),) +
                   ggtitle(paste0(nm," (n=",nrow(x),", k=",length(unique(x$Cluster)),")")) 
               return(p)
})
names(doublet_plots) <- smps
plot_grid(plotlist=doublet_plots)

Doublet clusters have a larger library size

pD <- colData(readRDS("../data/Robjects/SCE_QC_norm.rds"))
pD <- data.frame(pD)
library(dplyr)
ddatadf <- left_join(data.frame(ddatadf),pD[,c("barcode","UmiSums","GenesDetected","prcntMito")])
ddatadf$sampClust <- paste0(ddatadf$Sample,"_",ddatadf$Cluster)
ddatadf$isDoublet <- ifelse(ddatadf$sampClust %in% smp.dbltCluster,"Doublet","Singlet")
ggplot(ddatadf, aes(x=isDoublet,y=log10(UmiSums))) +
       geom_boxplot() 

Cluster batch corrected data

  • Next we will identify cells that are guilty-by-association by clustering the full dataset including the above identified doublet cells and find clusters with a high frequency of doublets
# Read in BC PCA
sce.cor <- readRDS("../data/Robjects/Corrected_preDoublet.rds")
m.cor <- reducedDim(sce.cor, type="corrected")
igr <- buildSNNGraph(m.cor,d=NA, transposed=TRUE)
# Overcluster
cl <- igraph::cluster_walktrap(igr,steps=2)
cluster <- cl$membership
clusDf <- data.frame("barcode"=rownames(m.cor),
             "fullCluster"=cluster)
ddatadf <- dplyr::left_join(ddatadf,clusDf)

Guilty-by-association clusters

  • All clusters with more than median+ 2x mad fraction of doublets are additionally identifed as doublet clusters
smry <- group_by(ddatadf, fullCluster, isDoublet) %>%
    summarize(NType=n()) %>%
    mutate(Prop=NType/sum(NType))

doubDef <- scater::isOutlier(smry$Prop[smry$isDoublet=="Doublet"],nmads=2,type="higher")
dblClusters <- smry$fullCluster[smry$isDoublet=="Doublet"][doubDef]
thrsld <- attributes(doubDef)[[1]][2]

ggplot(smry, aes(x=fullCluster, y=Prop, fill=isDoublet)) +
    geom_bar(stat="identity") +
    geom_hline(yintercept=1-thrsld) +
    scale_fill_manual(values=c("black","red"))

ump <- reducedDim(sce.cor, type="umap")
ump <- data.frame("barcode"=rownames(ump),
          "UMAP1"=ump[,1],
          "UMAP2"=ump[,2])
ddatadf <- left_join(data.frame(ddatadf),ump)

ggplot(ddatadf, aes(x=UMAP1, y=UMAP2, color=ddatadf$fullCluster %in% dblClusters)) +
    geom_point() +
    scale_color_manual(values=c("black","red")) +
    theme(legend.position="none")

Highlighting all cells identified as doublets

dbl.barcodes <- ddatadf$barcode[ddatadf$isDoublet=="Doublet" | ddatadf$fullCluster %in% dblClusters]

ddatadf$isDoubletFinal <- ddatadf$barcode %in% dbl.barcodes
ggplot(ddatadf, aes(x=UMAP1, y=UMAP2, color=ddatadf$isDoubletFinal)) +
    geom_point() +
    scale_color_manual(values=c("black","red")) +
    theme(legend.position="none")

  • As expected sample with more cells tend to have a higher frequency of doublets.
smry <- group_by(ddatadf, Sample, isDoubletFinal) %>%
    summarize(n=n()) %>%
    mutate(DoubletFraction=n/sum(n),totCells=sum(n)) %>%
    ungroup() %>%
    filter(isDoubletFinal)

ggplot(smry, aes(x=totCells,y=DoubletFraction)) +
    geom_point()

Other outliers

  • To try to identify damaged cells I looked at % of mitochondrial reads versus library size
  • The idea being that clusters with very low mitochondrial reads can be damaged cells/ stripped nuclei
  • Although there are some clsuters with low % of mito reads one cluster stood out in partiuclar and will be removed
m <- logcounts(readRDS("../data/Robjects/SCE_QC_norm.rds"))
hbb.exp <- colSums(m[c("Hbb-bt","Hbb-bs","Hbb-bh1"),])
hbb.exp <- data.frame("barcode"=names(hbb.exp),
              "hbbExp"=hbb.exp)
ddatadf <- left_join(ddatadf,hbb.exp)
smry <- group_by(ddatadf, sampClust) %>%
    summarize(medUmi=median(UmiSums),
          medprcntMito=median(prcntMito),
          medGd=median(GenesDetected),
          medHb=median(hbbExp)) %>%
    ungroup() %>%
    as.data.frame()

ggplot(smry, aes(x=medUmi,y=medprcntMito)) +
    geom_point() +
    scale_x_log10() +
    scale_y_log10() +
    geom_hline(yintercept=0.003) +
    xlab("Library Size") +
    ylab("Fraction of Mitochondrial Reads")

  • However in the UMAP below I am not fully convinced that they are all truly damaged cells.
  • If they were I would expect them to “stand out” more.
  • I did label cells as “potentially damaged” and will keep that information for further analyses.
cls <- smry[smry$medprcntMito<0.003,"sampClust"]
smps.dmgd <- unlist(lapply(strsplit(cls,split="_"),function(x) paste0(x[1],"_",x[2])))
cls.dmgd <- unlist(lapply(strsplit(cls,split="_"),function(x) x[3]))

dmgd_plots <- lapply(1:length(smps.dmgd), function(i) {
           nm <- smps.dmgd[i]
           x <- data.frame(ddata[[nm]])
           p <- ggplot(x, aes(x=SubUMAP1, y=SubUMAP2)) +
                   geom_point(color="grey80",size=0.7) +
                   geom_point(data=x[x$Cluster %in% cls.dmgd[i],], aes(color=factor(Cluster))) +
                   #                                scale_color_manual(values=wes_palette("FantasticFox1",length(unique(x$Cluster)),type="continuous")) +
                   theme(legend.position="none", panel.background = element_rect(colour = "black", linetype = 1, size = 0.5),
                     axis.line = element_blank(),
                     axis.ticks = element_blank(),
                     axis.text = element_blank(),
                     axis.title = element_blank(),) +
                   ggtitle(paste0(nm," (n=",nrow(x),", k=",length(unique(x$Cluster)),")")) 
               return(p)
})
names(dmgd_plots) <- smps.dmgd
plot_grid(plotlist=dmgd_plots)

dmgd <- ddatadf$barcode[ddatadf$sampClust %in% cls]
ddatadf$isPotDamaged <- ddatadf$barcode %in% dmgd
  • Finally I exclude clusters that show expression of hemoglobin
library(ggrepel)
ggplot(smry, aes(x=sampClust,y=medHb)) +
    geom_point() +
    xlab("Per-Sample Clusters") +
    ylab("Median Hbb Expression") +
    geom_label_repel(data=smry[smry$medHb>0.01,],aes(label=sampClust)) +
    theme(axis.text.x=element_blank())

rbcs <- smry[smry$medHb>0.01,"sampClust"]
rbcs <- ddatadf$barcode[ddatadf$sampClust %in% rbcs]

Save

ddatadf$isRbc <- ddatadf$barcode %in% rbcs
qcMets <- ddatadf[,c("barcode","DbltScore","isRbc","isDoubletFinal","isPotDamaged")]
write.csv(qcMets,"../data/Robjects/QC_Part2.csv")
LS0tCnRpdGxlOiAiRG91YmxldCBDYWxsaW5nIgphdXRob3I6ICJLYXJzdGVuIEJhY2giCmRhdGU6ICdgciBTeXMuRGF0ZSgpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMgogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0aGVtZTogam91cm5hbAogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoqKioKCkluIHRoaXMgZG9jdW1lbnQgSSB3aWxsIGZ1cnRoZXIgY2xlYW4gdXAgdGhlIGRhdGEgYW5kIHJlbW92ZSBjZWxscyB0aGF0IG1vc3QgbGlrZWx5IHJlcHJlc2VudCBkb3VibGV0cywgZGFtYWdlZCBjZWxscyBvciByZWQgYmxvb2QgY2VsbCBjb250YW1pbmF0aW9uLgoKCiMgUmVtb3ZhbCBvZiBkb3VibGV0cwpUaGUgZm9sbG93aW5nIHN0cmF0ZWd5IGZvciBkb3VibGVkIHJlbW92YWwgd2FzIGVtcGxveWVkLCBzb21ld2hhdCBmb2xsb3dpbmcgYW4gYXBwcm9hY2ggZnJvbSBfX1BpanVhbmEtU2FsYSwgR3JpZmZpdGhzIGFuZCBHdWliZW50aWYgZXQgYWwuX18KCjEuIEVzdGltYXRlIHByb2JhYmlsaXR5IG9mIGNlbGwgYmVpbmcgYSBkb3VibGV0IHBlciBzYW1wbGUgdXNpbmcgZG91YmxldENlbGxzIGZyb20gc2NyYW4uCjIuIENvbXB1dGUgcGVyIHNhbXBsZSBjbHVzdGVycyBiYXNlZCBvbiBhbGwgZ2VuZXMgYmFzZWQgb24gYSBTTk5HcmFwaCAoaz0xMCkgYW5kIHdhbGt0cmFwIGNsdXN0ZXJpbmcgd2l0aCBoaWdoIHJlc29sdXRpb24gKHN0ZXBzPTIsIHNlZSBgMDVfY29tcHV0ZURvdWJsZXRTY29yZXMuUmApLgozLiBDbHVzdGVycyB3aXRoIGhpZ2ggYXZlcmFnZSBkb3VibGV0IHByb2JhYmlsaXR5IGFyZSBpZGVudGlmaWVkIGFzIGRvdWJsZXRzLgo0LiBBbGwgY2VsbHMgYW5kIHNhbXBsZXMgKGluY2wuIGRvdWJsZXRzKSBhcmUgaW50ZWdyYXRlZCB1c2luZyBmYXN0TU5OLCBjZWxscyB0aGF0IGNsdXN0ZXIgd2l0aCAia25vd24iIGRvdWJsZXRzIGNlbGxzIGFyZSBndWlsdHkgYnkgYXNzb2NpYXRpb24uCgojIyBQZXIgc2FtcGxlIGNsdXN0ZXJzCi0gQmVsb3cgYXJlIHRoZSBwZXIgc2FtcGxlIGNsdXN0ZXJzIGFzIHdlbGwgYXMgdGhlIGRvdWJsZXQgc2NvcmVzIHBlciBjZWxsLgotIEJhc2VkIG9uIHRoZSBVTUFQIHNob3dzIHBhdHRlcm5zIGluZGljYXRpdmUgb2YgYSBsb3Qgb2YgbXVsdGlwbGV0cyBXS0JSNzUuNGJNRzMuIExvb2tpbmcgYXQgdGhlIEJhcmNvZGUgcGxvdCBpbiBgMDFfTWFrZUNvdW50TWF0cml4Lm5iLmh0bWxgIGl0IGRvZXNuJ3Qgc2hvdyB0aGUgc2FtZSBjbGVhciBrbmVlIHBsb3QsIHBvdGVudGlhbGx5IGluZGljYXRpdmUgb2YgaGlnaCBsZXZlbHMgb2YgYmFja2dyb3VuZCBtUk5BLiBJIHdpbGwgbmVlZCB0byBzZWUgd2hldGhlciB0byBrZWVwIHRoaXMgc2FtcGxlcyBvciBub3QuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLGZpZy5oZWlnaHQ9MTcsZmlnLndpZHRoPTE3fQojIElkZW50aWZ5aW5nIGRvdWJsZXRzIHdpdGggZG91YmxldENsdXN0ZXIKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHNjcmFuKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkod2VzYW5kZXJzb24pCmxpYnJhcnkodmlyaWRpcykKdGhlbWVfc2V0KHRoZW1lX2Nvd3Bsb3QoKSkKCiMgUmVhZCBpbiBkYXRhCmRkYXRhIDwtIHJlYWRSRFMoIi4uL2RhdGEvUm9iamVjdHMvRG91YmxldERhdGEucmRzIikKCiMgQWRkIHNhbXBsZSBpbmRleCB0byBkYXRhIGZyYW1lcwojIEFsc28gSSBhbSBsb2cgdHJhbnNmb3JtaW5nIHRoZSBzY29yZXMgaGVyZSBhbHJlYWR5CiMgU2luY2UgQmlvYzEwIHRoZSBzY29yZXMgaGF2ZSBhIGRpZmZlcmVudCBkaXN0cmlidXRpb24sIHdpdGggYSBtdWNoIHdpZGVyIHJhbmdlCm5tcyA8LSBuYW1lcyhkZGF0YSkKZGRhdGEgPC0gbGFwcGx5KG5hbWVzKGRkYXRhKSwgZnVuY3Rpb24oeCkgewoJCWRmcyA8LSBkZGF0YVtbeF1dCgkJZGZzJFNhbXBsZSA8LSB4CgkJZGZzJERibHRTY29yZSA8LSBsb2cxMChkZnMkRGJsdFNjb3JlKQoJCXJldHVybihkZnMpCn0pCm5hbWVzKGRkYXRhKSA8LSBubXMKCiMgVmlzdWFsaXppbmcgdGhlIGNsdXN0ZXJpbmcKY2x1c3Rlcl9wbG90cyA8LSBsYXBwbHkobmFtZXMoZGRhdGEpLCBmdW5jdGlvbihubSkgewoJCSAgIHggPC0gZGF0YS5mcmFtZShkZGF0YVtbbm1dXSkKCQkgICBwIDwtIGdncGxvdCh4LCBhZXMoeD1TdWJVTUFQMSwgeT1TdWJVTUFQMiwgY29sb3I9ZmFjdG9yKENsdXN0ZXIpKSkgKwoJCQkgICAgICAgZ2VvbV9wb2ludChzaXplPTAuNykgKwoJCQkgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz13ZXNfcGFsZXR0ZSgiRmFudGFzdGljRm94MSIsbGVuZ3RoKHVuaXF1ZSh4JENsdXN0ZXIpKSx0eXBlPSJjb250aW51b3VzIikpICsKCQkJICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImJsYWNrIiwgbGluZXR5cGUgPSAxLCBzaXplID0gMC41KSwKCQkJCSAgICAgYXhpcy5saW5lID0gZWxlbWVudF9ibGFuaygpLAoJCQkJICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAoJCQkJICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCgkJCQkgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksKSArCgkJCSAgICAgICBnZ3RpdGxlKHBhc3RlMChubSwiIChuPSIsbnJvdyh4KSwiLCBrPSIsbGVuZ3RoKHVuaXF1ZSh4JENsdXN0ZXIpKSwiKSIpKSAKCQkJICAgcmV0dXJuKHApCn0pCm5hbWVzKGNsdXN0ZXJfcGxvdHMpIDwtIG5hbWVzKGRkYXRhKQpwbG90X2dyaWQocGxvdGxpc3Q9Y2x1c3Rlcl9wbG90cykKCiMgVmlzdWFsaXppbmcgdGhlIGRvdWJsZXQgc2NvcmVzCnNjb3JlX3Bsb3RzIDwtIGxhcHBseShuYW1lcyhkZGF0YSksIGZ1bmN0aW9uKG5tKSB7CgkJICAgeCA8LSBkYXRhLmZyYW1lKGRkYXRhW1tubV1dKQoJCSAgIHAgPC0gZ2dwbG90KHgsIGFlcyh4PVN1YlVNQVAxLCB5PVN1YlVNQVAyLCBjb2xvcj1EYmx0U2NvcmUpKSArCgkJCSAgICAgICBnZW9tX3BvaW50KHNpemU9MC43KSArCgkJCSAgICAgICBzY2FsZV9jb2xvcl92aXJpZGlzKCkgKwoJCQkgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBsaW5ldHlwZSA9IDEsIHNpemUgPSAwLjUpLAoJCQkJICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksCgkJCQkgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCgkJCQkgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKCQkJCSAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwpICsKCQkJICAgICAgIGdndGl0bGUocGFzdGUwKG5tLCIgKG49Iixucm93KHgpLCIsIGs9IixsZW5ndGgodW5pcXVlKHgkQ2x1c3RlcikpLCIpIikpIAoJCQkgICByZXR1cm4ocCkKfSkKbmFtZXMoc2NvcmVfcGxvdHMpIDwtIG5hbWVzKGRkYXRhKQpwbG90X2dyaWQocGxvdGxpc3Q9c2NvcmVfcGxvdHMpCmBgYAoKIyMgSWRlbnRpZnkgY2x1c3RlcnMgcGVyIHNhbXBsZXMgdGhhdCByZXByZXNlbnQgZG91YmxldHMKLSBDbHVzdGVycyB0aGF0IGhhZCBhIGhpZ2hlciB0aGFuIG1lZGlhbiArIDEuNXhtYWQgZG91YmxldCBzY29yZSBhbmQgd2hlcmUgcHJlc2VudCBhdCBhIGZyZXF1ZW5jeSBvZiBsZXNzIHRoYW4gNSUgd2hlcmUgaWRlbnRpZmllZCBhcyBkb3VibGV0IGNlbGxzCi0gMS41IGFzIHRoaXMgc2VlbWVkIHRvIHByb2R1Y2UgdGhlIG1vc3QgcmVhc29uYWJsZSByZXN1bHRzIGluIHRlcm1zIG9mIHN0cmluZ2VuY3kKLSBBdCB0aGlzIHBvaW50IEkgd2FudCB0byBiZSByYXRoZXIgc3RyaW5nZW50IGluIHRoZSBkZWZpbml0aW9uIGFzIGl0IGlzIG1vcmUgcHJvYmxlbWF0aWMgdG8gcmVtb3ZlIGJpb2xvZ2ljYWxseSByZWxldmFudCBjbHVzdGVycyB0aGFuIG1haW50YWluaW5nIGRvdWJsZXQgY2x1c3RlcnMgYXMgdGhleSBjYW4gc3RpbGwgYmUgZmxhZ2dlZCBsYXRlci4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIGZpZy53aWR0aD0xMH0KIyBJZGVudGlmeWluZyBkb3VibGV0cyBiYXNlZCBvbiBkb3VibGV0Q2VsbAoKZGRhdGFkZiA8LSBkYXRhLmZyYW1lKGRvLmNhbGwocmJpbmQsZGRhdGEpKQoKIyBDb21wdXRlIG1lZGlhbiBzY29yZXMgcGVyIGNsdXN0ZXIgYW5kIHNhbXBsZQptZWRzY29yZXMgPC0gYWdncmVnYXRlKGRkYXRhZGYkRGJsdFNjb3JlLCBsaXN0KGRkYXRhZGYkU2FtcGxlLCBkZGF0YWRmJENsdXN0ZXIpLCBtZWRpYW4pCm5hbWVzKG1lZHNjb3JlcykgPSBjKCJzYW1wbGUiLCAiY2x1c3RlciIsICJtZWRpYW4uc2NvcmUiKQoKbWVkc2NvcmVzJG4uY2VsbHMgPSBzYXBwbHkoMTpucm93KG1lZHNjb3JlcyksIGZ1bmN0aW9uKHJvdyl7CgkJCSAgICAgICBzdW0oZGRhdGFkZiRDbHVzdGVyID09IG1lZHNjb3JlcyRjbHVzdGVyW3Jvd10gJiBkZGF0YWRmJFNhbXBsZSA9PSBtZWRzY29yZXMkc2FtcGxlW3Jvd10pCn0pCgptZWRzY29yZXMkZnJhY0NlbGxzID0gc2FwcGx5KDE6bnJvdyhtZWRzY29yZXMpLCBmdW5jdGlvbihyb3cpewoJCQkJICBtZWRzY29yZXMkbi5jZWxsc1tyb3ddL3N1bShtZWRzY29yZXMkbi5jZWxsc1ttZWRzY29yZXMkc2FtcGxlID09IG1lZHNjb3JlcyRzYW1wbGVbcm93XV0pCn0pCgoKIyBEZWZpbmUgZG91YmxldHMgYmFzZWQgb24gZnJhY3Rpb24gYW5kIG1lZGlhbiBkb3VibGV0IHNjb3JlCm1lZHNjb3JlcyRvdXRsaWVyIDwtIHNjYXRlcjo6aXNPdXRsaWVyKG1lZHNjb3JlcyRtZWRpYW4uc2NvcmUsbm1hZD0xLjUsdHlwZT0iaGlnaGVyIixsb2c9RkFMU0UpCm1lZHNjb3JlcyRpc0RvdWJsZXQgPC0gbWVkc2NvcmVzJG91dGxpZXIgJiBtZWRzY29yZXMkZnJhY0NlbGxzIDw9IDAuMDUKCiMgUGxvdCBmcm9tIEpvaG5ueSB3aXRoIHRoZSBzaWduaWZpY2FudCBjbHVzdGVycyBoaWdobGlnaHRlZApnZ3Bsb3QobWVkc2NvcmVzLCBhZXMoeCA9IHNhbXBsZSwgeSA9IG1lZGlhbi5zY29yZSwgY29sID0gZmFjdG9yKGNsdXN0ZXIpKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IG1lZHNjb3Jlc1ttZWRzY29yZXMkaXNEb3VibGV0LF0pICsKICBnZW9tX2ppdHRlcihkYXRhID0gbWVkc2NvcmVzWyFtZWRzY29yZXMkaXNEb3VibGV0LF0sIGNvbCA9ICJkYXJrZ3JleSIsIHNpemUgPSAwLjQsIHdpZHRoID0gMC4yLCBoZWlnaHQgPSAwKSArCiAgZ3VpZGVzKGNvbG9yPSJub25lIikgKwogIGxhYnMoeCA9ICJTYW1wbGUiLCB5ID0gImNsdXN0ZXIgbWVkaWFuIHNjb3JlIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgptZWRzY29yZXMuZGJscyA8LSBtZWRzY29yZXNbbWVkc2NvcmVzJGlzRG91YmxldCxdCnNtcC5kYmx0Q2x1c3RlciA8LSBwYXN0ZShtZWRzY29yZXMuZGJscyRzYW1wbGUsbWVkc2NvcmVzLmRibHMkY2x1c3RlcixzZXA9Il8iKQpgYGAKCgojIyBWaXN1YWxpemluZyB0aGUgZG91YmxldCBjYWxsZWQgY2x1c3RlcnMKLSBtb3N0IGNsdXN0ZXJzIHNob3cgdGhlIHR5cGljYWwgdGVhciBzaGFwZSBpbiB0aGUgVU1BUAotIHNvbWUgY2x1c3RlcnMgYWxzbyBhcHBlYXIgdG8gYmVsIHNlbGYtZG91YmxldHMsIHdoaWNoIGFyZSBsZXNzIHByb2JsZW1hdGljIGJ1dCB3aWxsIHN0aWxsIGJlIHJlbW92ZWQKLSBob3dldmVyIHdlIGNhbiBhbHJlYWR5IHNlZSBoZXJlIHRoYXQgdGhlcmUgbWlnaHQgYmUgbW9yZSBkb3VibGV0IGNsdXN0ZXJzIHRoYW4gd2UgaWRlbnRpZmllZCBhYm92ZQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgZmlnLmhlaWdodD0xMywgZmlnLndpZHRoPTEzfQpzbXBzIDwtIG5hbWVzKGRkYXRhKVtuYW1lcyhkZGF0YSkgJWluJSBtZWRzY29yZXMuZGJscyRzYW1wbGVdCmRvdWJsZXRfcGxvdHMgPC0gbGFwcGx5KHNtcHMsIGZ1bmN0aW9uKG5tKSB7CgkJICAgeCA8LSBkYXRhLmZyYW1lKGRkYXRhW1tubV1dKQoJCSAgIHAgPC0gZ2dwbG90KHgsIGFlcyh4PVN1YlVNQVAxLCB5PVN1YlVNQVAyKSkgKwoJCQkgICAgICAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleTgwIixzaXplPTAuNykgKwoJCQkgICAgICAgZ2VvbV9wb2ludChkYXRhPXhbeCRDbHVzdGVyICVpbiUgbWVkc2NvcmVzLmRibHNbbWVkc2NvcmVzLmRibHMkc2FtcGxlPT1ubSwiY2x1c3RlciJdLF0sIGFlcyhjb2xvcj1mYWN0b3IoQ2x1c3RlcikpKSArCgkJCSAgICAgICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPXdlc19wYWxldHRlKCJGYW50YXN0aWNGb3gxIixsZW5ndGgodW5pcXVlKHgkQ2x1c3RlcikpLHR5cGU9ImNvbnRpbnVvdXMiKSkgKwoJCQkgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBsaW5ldHlwZSA9IDEsIHNpemUgPSAwLjUpLAoJCQkJICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksCgkJCQkgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCgkJCQkgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKCQkJCSAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwpICsKCQkJICAgICAgIGdndGl0bGUocGFzdGUwKG5tLCIgKG49Iixucm93KHgpLCIsIGs9IixsZW5ndGgodW5pcXVlKHgkQ2x1c3RlcikpLCIpIikpIAoJCQkgICByZXR1cm4ocCkKfSkKbmFtZXMoZG91YmxldF9wbG90cykgPC0gc21wcwpwbG90X2dyaWQocGxvdGxpc3Q9ZG91YmxldF9wbG90cykKYGBgCgojIyBEb3VibGV0IGNsdXN0ZXJzIGhhdmUgYSBsYXJnZXIgbGlicmFyeSBzaXplCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpwRCA8LSBjb2xEYXRhKHJlYWRSRFMoIi4uL2RhdGEvUm9iamVjdHMvU0NFX1FDX25vcm0ucmRzIikpCnBEIDwtIGRhdGEuZnJhbWUocEQpCmxpYnJhcnkoZHBseXIpCmRkYXRhZGYgPC0gbGVmdF9qb2luKGRhdGEuZnJhbWUoZGRhdGFkZikscERbLGMoImJhcmNvZGUiLCJVbWlTdW1zIiwiR2VuZXNEZXRlY3RlZCIsInByY250TWl0byIpXSkKZGRhdGFkZiRzYW1wQ2x1c3QgPC0gcGFzdGUwKGRkYXRhZGYkU2FtcGxlLCJfIixkZGF0YWRmJENsdXN0ZXIpCmRkYXRhZGYkaXNEb3VibGV0IDwtIGlmZWxzZShkZGF0YWRmJHNhbXBDbHVzdCAlaW4lIHNtcC5kYmx0Q2x1c3RlciwiRG91YmxldCIsIlNpbmdsZXQiKQpnZ3Bsb3QoZGRhdGFkZiwgYWVzKHg9aXNEb3VibGV0LHk9bG9nMTAoVW1pU3VtcykpKSArCiAgICAgICBnZW9tX2JveHBsb3QoKSAKYGBgCgojIyBDbHVzdGVyIGJhdGNoIGNvcnJlY3RlZCBkYXRhCi0gTmV4dCB3ZSB3aWxsIGlkZW50aWZ5IGNlbGxzIHRoYXQgYXJlIGd1aWx0eS1ieS1hc3NvY2lhdGlvbiBieSBjbHVzdGVyaW5nIHRoZSBmdWxsIGRhdGFzZXQgaW5jbHVkaW5nIHRoZSBhYm92ZSBpZGVudGlmaWVkIGRvdWJsZXQgY2VsbHMgYW5kIGZpbmQgY2x1c3RlcnMgd2l0aCBhIGhpZ2ggZnJlcXVlbmN5IG9mIGRvdWJsZXRzCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIFJlYWQgaW4gQkMgUENBCnNjZS5jb3IgPC0gcmVhZFJEUygiLi4vZGF0YS9Sb2JqZWN0cy9Db3JyZWN0ZWRfcHJlRG91YmxldC5yZHMiKQptLmNvciA8LSByZWR1Y2VkRGltKHNjZS5jb3IsIHR5cGU9ImNvcnJlY3RlZCIpCmlnciA8LSBidWlsZFNOTkdyYXBoKG0uY29yLGQ9TkEsIHRyYW5zcG9zZWQ9VFJVRSkKIyBPdmVyY2x1c3RlcgpjbCA8LSBpZ3JhcGg6OmNsdXN0ZXJfd2Fsa3RyYXAoaWdyLHN0ZXBzPTIpCmNsdXN0ZXIgPC0gY2wkbWVtYmVyc2hpcApjbHVzRGYgPC0gZGF0YS5mcmFtZSgiYmFyY29kZSI9cm93bmFtZXMobS5jb3IpLAoJCSAgICAgImZ1bGxDbHVzdGVyIj1jbHVzdGVyKQpkZGF0YWRmIDwtIGRwbHlyOjpsZWZ0X2pvaW4oZGRhdGFkZixjbHVzRGYpCmBgYAoKCiMjIEd1aWx0eS1ieS1hc3NvY2lhdGlvbiBjbHVzdGVycwotIEFsbCBjbHVzdGVycyB3aXRoIG1vcmUgdGhhbiBtZWRpYW4rIDJ4IG1hZCBmcmFjdGlvbiBvZiBkb3VibGV0cyBhcmUgYWRkaXRpb25hbGx5IGlkZW50aWZlZCBhcyBkb3VibGV0IGNsdXN0ZXJzCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzbXJ5IDwtIGdyb3VwX2J5KGRkYXRhZGYsIGZ1bGxDbHVzdGVyLCBpc0RvdWJsZXQpICU+JQogICAgc3VtbWFyaXplKE5UeXBlPW4oKSkgJT4lCiAgICBtdXRhdGUoUHJvcD1OVHlwZS9zdW0oTlR5cGUpKQoKZG91YkRlZiA8LSBzY2F0ZXI6OmlzT3V0bGllcihzbXJ5JFByb3Bbc21yeSRpc0RvdWJsZXQ9PSJEb3VibGV0Il0sbm1hZHM9Mix0eXBlPSJoaWdoZXIiKQpkYmxDbHVzdGVycyA8LSBzbXJ5JGZ1bGxDbHVzdGVyW3NtcnkkaXNEb3VibGV0PT0iRG91YmxldCJdW2RvdWJEZWZdCnRocnNsZCA8LSBhdHRyaWJ1dGVzKGRvdWJEZWYpW1sxXV1bMl0KCmdncGxvdChzbXJ5LCBhZXMoeD1mdWxsQ2x1c3RlciwgeT1Qcm9wLCBmaWxsPWlzRG91YmxldCkpICsKICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTEtdGhyc2xkKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiYmxhY2siLCJyZWQiKSkKCnVtcCA8LSByZWR1Y2VkRGltKHNjZS5jb3IsIHR5cGU9InVtYXAiKQp1bXAgPC0gZGF0YS5mcmFtZSgiYmFyY29kZSI9cm93bmFtZXModW1wKSwKCQkgICJVTUFQMSI9dW1wWywxXSwKCQkgICJVTUFQMiI9dW1wWywyXSkKZGRhdGFkZiA8LSBsZWZ0X2pvaW4oZGF0YS5mcmFtZShkZGF0YWRmKSx1bXApCgpnZ3Bsb3QoZGRhdGFkZiwgYWVzKHg9VU1BUDEsIHk9VU1BUDIsIGNvbG9yPWRkYXRhZGYkZnVsbENsdXN0ZXIgJWluJSBkYmxDbHVzdGVycykpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImJsYWNrIiwicmVkIikpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCmBgYAoKIyMgSGlnaGxpZ2h0aW5nIGFsbCBjZWxscyBpZGVudGlmaWVkIGFzIGRvdWJsZXRzCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpkYmwuYmFyY29kZXMgPC0gZGRhdGFkZiRiYXJjb2RlW2RkYXRhZGYkaXNEb3VibGV0PT0iRG91YmxldCIgfCBkZGF0YWRmJGZ1bGxDbHVzdGVyICVpbiUgZGJsQ2x1c3RlcnNdCgpkZGF0YWRmJGlzRG91YmxldEZpbmFsIDwtIGRkYXRhZGYkYmFyY29kZSAlaW4lIGRibC5iYXJjb2RlcwpnZ3Bsb3QoZGRhdGFkZiwgYWVzKHg9VU1BUDEsIHk9VU1BUDIsIGNvbG9yPWRkYXRhZGYkaXNEb3VibGV0RmluYWwpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJibGFjayIsInJlZCIpKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQpgYGAKCi0gQXMgZXhwZWN0ZWQgc2FtcGxlIHdpdGggbW9yZSBjZWxscyB0ZW5kIHRvIGhhdmUgYSBoaWdoZXIgZnJlcXVlbmN5IG9mIGRvdWJsZXRzLgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0Kc21yeSA8LSBncm91cF9ieShkZGF0YWRmLCBTYW1wbGUsIGlzRG91YmxldEZpbmFsKSAlPiUKICAgIHN1bW1hcml6ZShuPW4oKSkgJT4lCiAgICBtdXRhdGUoRG91YmxldEZyYWN0aW9uPW4vc3VtKG4pLHRvdENlbGxzPXN1bShuKSkgJT4lCiAgICB1bmdyb3VwKCkgJT4lCiAgICBmaWx0ZXIoaXNEb3VibGV0RmluYWwpCgpnZ3Bsb3Qoc21yeSwgYWVzKHg9dG90Q2VsbHMseT1Eb3VibGV0RnJhY3Rpb24pKSArCiAgICBnZW9tX3BvaW50KCkKYGBgCgojIE90aGVyIG91dGxpZXJzCi0gVG8gdHJ5IHRvIGlkZW50aWZ5IGRhbWFnZWQgY2VsbHMgSSBsb29rZWQgYXQgJSBvZiBtaXRvY2hvbmRyaWFsIHJlYWRzIHZlcnN1cyBsaWJyYXJ5IHNpemUKLSBUaGUgaWRlYSBiZWluZyB0aGF0IGNsdXN0ZXJzIHdpdGggdmVyeSBsb3cgbWl0b2Nob25kcmlhbCByZWFkcyBjYW4gYmUgZGFtYWdlZCBjZWxscy8gc3RyaXBwZWQgbnVjbGVpCi0gQWx0aG91Z2ggdGhlcmUgYXJlIHNvbWUgY2xzdXRlcnMgd2l0aCBsb3cgJSBvZiBtaXRvIHJlYWRzIG9uZSBjbHVzdGVyIHN0b29kIG91dCBpbiBwYXJ0aXVjbGFyIGFuZCB3aWxsIGJlIHJlbW92ZWQKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptIDwtIGxvZ2NvdW50cyhyZWFkUkRTKCIuLi9kYXRhL1JvYmplY3RzL1NDRV9RQ19ub3JtLnJkcyIpKQpoYmIuZXhwIDwtIGNvbFN1bXMobVtjKCJIYmItYnQiLCJIYmItYnMiLCJIYmItYmgxIiksXSkKaGJiLmV4cCA8LSBkYXRhLmZyYW1lKCJiYXJjb2RlIj1uYW1lcyhoYmIuZXhwKSwKCQkgICAgICAiaGJiRXhwIj1oYmIuZXhwKQpkZGF0YWRmIDwtIGxlZnRfam9pbihkZGF0YWRmLGhiYi5leHApCnNtcnkgPC0gZ3JvdXBfYnkoZGRhdGFkZiwgc2FtcENsdXN0KSAlPiUKICAgIHN1bW1hcml6ZShtZWRVbWk9bWVkaWFuKFVtaVN1bXMpLAoJICAgICAgbWVkcHJjbnRNaXRvPW1lZGlhbihwcmNudE1pdG8pLAoJICAgICAgbWVkR2Q9bWVkaWFuKEdlbmVzRGV0ZWN0ZWQpLAoJICAgICAgbWVkSGI9bWVkaWFuKGhiYkV4cCkpICU+JQogICAgdW5ncm91cCgpICU+JQogICAgYXMuZGF0YS5mcmFtZSgpCgpnZ3Bsb3Qoc21yeSwgYWVzKHg9bWVkVW1pLHk9bWVkcHJjbnRNaXRvKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBzY2FsZV95X2xvZzEwKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAuMDAzKSArCiAgICB4bGFiKCJMaWJyYXJ5IFNpemUiKSArCiAgICB5bGFiKCJGcmFjdGlvbiBvZiBNaXRvY2hvbmRyaWFsIFJlYWRzIikKYGBgCgotIEhvd2V2ZXIgaW4gdGhlIFVNQVAgYmVsb3cgSSBhbSBub3QgZnVsbHkgY29udmluY2VkIHRoYXQgdGhleSBhcmUgYWxsIHRydWx5IGRhbWFnZWQgY2VsbHMuCi0gSWYgdGhleSB3ZXJlIEkgd291bGQgZXhwZWN0IHRoZW0gdG8gInN0YW5kIG91dCIgbW9yZS4KLSBJIGRpZCBsYWJlbCBjZWxscyBhcyAicG90ZW50aWFsbHkgZGFtYWdlZCIgYW5kIHdpbGwga2VlcCB0aGF0IGluZm9ybWF0aW9uIGZvciBmdXJ0aGVyIGFuYWx5c2VzLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0KY2xzIDwtIHNtcnlbc21yeSRtZWRwcmNudE1pdG88MC4wMDMsInNhbXBDbHVzdCJdCnNtcHMuZG1nZCA8LSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KGNscyxzcGxpdD0iXyIpLGZ1bmN0aW9uKHgpIHBhc3RlMCh4WzFdLCJfIix4WzJdKSkpCmNscy5kbWdkIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQoY2xzLHNwbGl0PSJfIiksZnVuY3Rpb24oeCkgeFszXSkpCgpkbWdkX3Bsb3RzIDwtIGxhcHBseSgxOmxlbmd0aChzbXBzLmRtZ2QpLCBmdW5jdGlvbihpKSB7CgkJICAgbm0gPC0gc21wcy5kbWdkW2ldCgkJICAgeCA8LSBkYXRhLmZyYW1lKGRkYXRhW1tubV1dKQoJCSAgIHAgPC0gZ2dwbG90KHgsIGFlcyh4PVN1YlVNQVAxLCB5PVN1YlVNQVAyKSkgKwoJCQkgICAgICAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleTgwIixzaXplPTAuNykgKwoJCQkgICAgICAgZ2VvbV9wb2ludChkYXRhPXhbeCRDbHVzdGVyICVpbiUgY2xzLmRtZ2RbaV0sXSwgYWVzKGNvbG9yPWZhY3RvcihDbHVzdGVyKSkpICsKCQkJICAgICAgICMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9d2VzX3BhbGV0dGUoIkZhbnRhc3RpY0ZveDEiLGxlbmd0aCh1bmlxdWUoeCRDbHVzdGVyKSksdHlwZT0iY29udGludW91cyIpKSArCgkJCSAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLCBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJibGFjayIsIGxpbmV0eXBlID0gMSwgc2l6ZSA9IDAuNSksCgkJCQkgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwKCQkJCSAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKCQkJCSAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAoJCQkJICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLCkgKwoJCQkgICAgICAgZ2d0aXRsZShwYXN0ZTAobm0sIiAobj0iLG5yb3coeCksIiwgaz0iLGxlbmd0aCh1bmlxdWUoeCRDbHVzdGVyKSksIikiKSkgCgkJCSAgIHJldHVybihwKQp9KQpuYW1lcyhkbWdkX3Bsb3RzKSA8LSBzbXBzLmRtZ2QKcGxvdF9ncmlkKHBsb3RsaXN0PWRtZ2RfcGxvdHMpCmRtZ2QgPC0gZGRhdGFkZiRiYXJjb2RlW2RkYXRhZGYkc2FtcENsdXN0ICVpbiUgY2xzXQpkZGF0YWRmJGlzUG90RGFtYWdlZCA8LSBkZGF0YWRmJGJhcmNvZGUgJWluJSBkbWdkCmBgYAoKLSBGaW5hbGx5IEkgZXhjbHVkZSBjbHVzdGVycyB0aGF0IHNob3cgZXhwcmVzc2lvbiBvZiBoZW1vZ2xvYmluCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGdncmVwZWwpCmdncGxvdChzbXJ5LCBhZXMoeD1zYW1wQ2x1c3QseT1tZWRIYikpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICB4bGFiKCJQZXItU2FtcGxlIENsdXN0ZXJzIikgKwogICAgeWxhYigiTWVkaWFuIEhiYiBFeHByZXNzaW9uIikgKwogICAgZ2VvbV9sYWJlbF9yZXBlbChkYXRhPXNtcnlbc21yeSRtZWRIYj4wLjAxLF0sYWVzKGxhYmVsPXNhbXBDbHVzdCkpICsKICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkKcmJjcyA8LSBzbXJ5W3NtcnkkbWVkSGI+MC4wMSwic2FtcENsdXN0Il0KcmJjcyA8LSBkZGF0YWRmJGJhcmNvZGVbZGRhdGFkZiRzYW1wQ2x1c3QgJWluJSByYmNzXQpgYGAKIyBTYXZlCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkZGF0YWRmJGlzUmJjIDwtIGRkYXRhZGYkYmFyY29kZSAlaW4lIHJiY3MKcWNNZXRzIDwtIGRkYXRhZGZbLGMoImJhcmNvZGUiLCJEYmx0U2NvcmUiLCJpc1JiYyIsImlzRG91YmxldEZpbmFsIiwiaXNQb3REYW1hZ2VkIildCndyaXRlLmNzdihxY01ldHMsIi4uL2RhdGEvUm9iamVjdHMvUUNfUGFydDIuY3N2IikKYGBgCg==